// extern crate bindgen;

use std::env;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

fn main() {
    // Tell cargo to tell rustc to link the system bzip2
    // shared library.
    println!("cargo:rustc-link-lib=bz2");

    // Tell cargo to invalidate the built crate whenever the wrapper changes
    println!("cargo:rerun-if-changed=wrapper.h");

    // The bindgen::Builder is the main entry point
    // to bindgen, and lets you build up options for
    // the resulting bindings.
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate
        // bindings for.
        .header("wrapper.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        // Finish the builder and generate the bindings.
        .generate()
        // Unwrap the Result and panic on failure.
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    // build_c();
    build_c_with_bindgen();
    build_cpp_with_bindgen();
    build_cpp_route_with_bindgen();
}

extern crate cc;
fn build_c() {
    cc::Build::new().file("src/c/lib/c_api.c").compile("c_api");
    println!("cargo:rerun-if-changed=src/c/lib/c_api.c");
    println!("cargo:rerun-if-changed=src/c/lib/c_api.h");
}

fn build_c_with_bindgen() {
    build_c();
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindgen::Builder::default()
        .header("src/c/lib/c_api.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(out_path.join("c_api.rs"))
        .expect("Couldn't write bindings!");
}

fn build_cpp_with_bindgen() {
    // ref: https://github.com/alexcrichton/cc-rs/issues/250
    // 实际上`cc`不支持创建动态库
    cc::Build::new()
        .file("src/c/lib/api.cpp")
        .cpp(true)
        .cpp_set_stdlib("c++")
        .flag("-std=c++11")
        // .shared_flag(true)
        // .pic(true)
        // .use_plt(true)
        .compile("api");
    println!("cargo:rerun-if-changed=src/c/lib/api.hpp");
    println!("cargo:rerun-if-changed=src/c/lib/api.cpp");
    // println!("cargo:rustc-link-lib=dylib=api");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindgen::Builder::default()
        .header("src/c/lib/api.hpp")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(out_path.join("api.rs"))
        .expect("Couldn't write bindings!");
}

fn build_cpp_route_with_bindgen() {
    // 拷贝动态库文件
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindgen::Builder::default()
        .header("src/c/library-bridge.hpp")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(out_path.join("route.rs"))
        .expect("Couldn't write bindings!");
    let to = out_path
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .join("librout_server.dylib");
    let output_dir_buffer = out_path
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .parent()
        .unwrap();
    let from = "src/c/lib/librout_server.dylib";
    println!("to={:?}", to.to_str());
    println!("from={}", from);
    let ret = std::fs::copy(from, to);
    match ret {
        Ok(_) => (),
        Err(_) => panic!("error"),
    }

    cc::Build::new()
        .file("src/c/library-bridge.cpp")
        .cpp(true)
        .cpp_link_stdlib("stdc++")
        .cpp_set_stdlib("c++")
        .flag("-std=c++11")
        .compile("route");
    println!("cargo:rerun-if-changed=src/c/library-bridge.hpp");
    println!("cargo:rerun-if-changed=src/c/library-bridge.cpp");
    // println!("cargo:rerun-if-changed=src/c/lib/routing_server.h");
    println!(
        "cargo:rustc-link-search=native={}",
        output_dir_buffer.to_str().expect("")
    );
    println!("cargo:rustc-link-lib=dylib=rout_server");

    
    // 下面这个方法，无法正常更新可执行程序的依赖目录
    // 原因是更新发生在可执行程序生成之前
    // sh -c install_name_tool -add_rpath @executable_path /Users/zuoyu/tmp/rust/test4/xxxxbindgen_demo/target/debug/xxxxbindgen_demo
    let executable_path_buffer = env::current_exe().expect("executable_path");
    let executable_path = executable_path_buffer
        .as_path()
        .as_os_str()
        .to_str()
        .expect("executable");
    let output_dir: String = String::from(output_dir_buffer.as_os_str().to_str().expect(""));
    let executable_path_2 = output_dir + "/xxxxbindgen_demo";
    let executable_path_3 =
        "/Users/zuoyu/tmp/rust/test4/xxxxbindgen_demo/target/debug/xxxxbindgen_demo";
    let executable_path_exist = std::path::Path::new(&executable_path_3).exists();
    if false == executable_path_exist {
        // panic!("not exist");
    }
    let rpath = "@executable_path";
    let executable = executable_path_3;
    let mut params = String::from("install_name_tool -add_rpath ");
    params += rpath;
    params += " ";
    params += executable;
    let output = Command::new("sh").arg("-c").arg(params).output();

    match output {
        Ok(_) => {}
        Err(error) => panic!("{}", error),
    }

    // panic!("output");
}
